#include "edit_deployment_interface.h"
#include "table.h"
#include "menu.h"
#include "io.h"
#include "deployment.h"
#include "template_set.h"
#include "ship_template.h"
#include "slot_template.h"
#include "fleet.h"
#include "ship_types.h"
#include "slot_types.h"
#include "ship.h"
#include <fstream>
#include "json/writer.h"
#include <stdexcept>

using namespace std;

#define __DEP (*(getDeployment()))
// This can't be called unless a Deployment is available!
#define __SET (*(getSet()))

MainDeploymentIntro::MainDeploymentIntro( FSM * _fsm ) : FSMState(_fsm) {stateName = "MainDeploymentIntro";}

int MainDeploymentIntro::update() {
			vector<string> choices = { "quit", "load a new Deployment", "create a new Deployment" };
			if ( __DEP ) {
				choices.push_back("inspect Deployment: " + __DEP->name);
				choices.push_back("save Deployment: " + __DEP->name);
				choices.push_back("edit Deployment: "+__DEP->name+" templates");
			}
			int choice = Menu::print(choices, false);

			switch ( choice ) {
				// Quit program
				case 1:
					return -1;
				// Load new Deployment
				case 2: {
						string filename;
						cout << "Please enter the name of the json file that contains the Deployment:\n\n";
						getline(cin, filename); cout << "\n";

						try {
							Json::Value deployments = IO::loadDeployment(filename);
							vector<string> depNames;

							// List all Deployments in file
							for ( unsigned int i = 0; i < deployments.size(); i++)
								depNames.push_back(deployments[i]["Name"].asString());

							int choice = Menu::print(depNames,true,"select Deployment")-1;
							if ( choice == -1 ) break;

							if (__DEP) 	delete (__SET);
							delete (__DEP);
							__DEP = new Deployment(deployments[choice]);

							if ( deployments[choice].isMember("TemplateSet") ) {
								__SET = new (nothrow) TemplateSet(deployments[choice]["TemplateSet"]);
							}


						} catch ( runtime_error e ) {
							cout << "\nFailed to load the Deployment\n" << e.what() << "\n\n";
						}
						catch ( bad_alloc e ) {
							cout << "\nFailed to allocate memory!\n" << e.what() << "\n\n";
						}
					}
					break;
				// Create new Deployment
				case 3:
					if ( __DEP ) delete __SET;
					delete __DEP;
					__DEP = new Deployment();
					cout << "Please enter the name of your Deployment:\n\n";
					getline(cin, __DEP->name); cout << "\n";
					break;
				// Inspect Deployment
				case 4:
					fsm->transitionTo("InspectDeployment");
					break;
				// Save Deployment
				case 5: {
						string filename;
						cout << "Please enter the name of the json file that will contain the Deployment (note that you can write in files with other save types too):\n\n";
						getline(cin, filename); cout << "\n";
						Json::Value deployments;
						try {
							deployments = IO::loadJson(filename);
							bool save = true;
							int index = deployments.size();
							for ( unsigned int i = 0; i < deployments.size(); i++ ) {
								if ( deployments[i]["Type"] == "Deployment" && 
									 deployments[i]["Name"].asString() == __DEP->name ) {
									cout << "Found a Deployment with the same name at index ["<< i+1 << "] in this file. Do you want to replace it or keep it?\n\n";
									vector<string> kr = {"replace the existing Deployment", "keep both without changing names"};
									int choice = Menu::print(kr, true);
									if ( choice == 0 ) { save = false; break; }
									else if ( choice == 1 ) { index = i; break; }	
								}
							}

							if ( !save ) break;

							deployments[index] = __DEP->convertToJson();
							if ( __SET ) deployments[index]["TemplateSet"] = __SET->convertToJson();
						}
						catch ( FiletypeError e ) {
							// This file does not contain deployments, we will just append it
							deployments[deployments.size()] = __DEP->convertToJson();
							if ( __SET ) deployments[deployments.size()] = __SET->convertToJson();
							break;
						}
						catch ( runtime_error e ) {
							// Prob new file
							cout << "Writing on new file..\n\n";

							deployments[0u] = __DEP->convertToJson();
							if ( __SET ) deployments[0u]["TemplateSet"] = __SET->convertToJson();
						}
						ofstream file(filename, ios_base::trunc );
						file << deployments;
						file.close();
					}
					break;
				// Edit Templates
				case 6:
					fsm->transitionTo("MainTemplateIntro");
					break;
			}
			return choice;
		}

//-------------------------

InspectDeployment::InspectDeployment(FSM * _fsm) : FSMState(_fsm) {stateName="InspectDeployment";}

void InspectDeployment::doEnter(int param) {
	Table::print(*__DEP);
}

int InspectDeployment::update() {
	int fleetOptions = 1, tSetOptions = 1;
	vector<string> choices = { "reprint the Deployment", "rename the Deployment", "add a new Fleet", "select a Fleet"};

	if ( __DEP->getRealFleetsNumber() > 0 ) {
		fleetOptions = 0;
		choices.push_back("remove a Fleet");
	}

	if ( __SET && __SET->getShipTemplatesNumber() > 0 ) {
		choices.push_back("add a new Ship");
		tSetOptions = 0;
	}
	else
		cout << "Note that you can't add any Ships to this Deployment because you do not have any available Ship Templates to build them from!\n\n";

	if ( __DEP->getShipsNumber() > 0 ) {
		choices.push_back("copy a Ship");
		choices.push_back("remove a Ship");
		choices.push_back("select a Ship");
		choices.push_back("change a Ship's Fleet");
	}
	

	int choice = Menu::print(choices, true);
	
	// Normalize choice as if all options were actually in the menu
	choice = choice > 4 ? choice + fleetOptions : choice;
	choice = choice > 5 ? choice + tSetOptions : choice;

	switch ( choice ) {
		// Back
		case 0:
			fsm->transitionBack();
			break;
		// Reprint Deployment
		case 1:
			Table::print(*__DEP);
			break;
		// Rename Deployment Set
		case 2:
			cout << "Please insert the new Deployment's name\n\n";
			getline(cin, __DEP->name); cout << "\n";
			break;
		// Add new Fleet
		case 3: {
			string fleetName;
			cout << "Please insert the new Fleet's name\n\n";
			getline(cin, fleetName); cout << "\n";
			__DEP->addFleet(fleetName);
			}
			break;
		// Inspect Fleet
		case 4:
			fsm->transitionTo("InspectFleet");
			break;
		// Remove Fleet
		case 5:
			try {
				__DEP->rmFleet(Menu::print(__DEP->getRealFleetNames(),true,"remove"));
				Table::print(*__DEP);
			}
			catch ( invalid_argument e ) {}
			break;
		// Add Ship 
		case 6: {
					const ShipTemplate * st;
					// Select Ship's Template
					try {
						cout << "Please select the Ship's Template:\n\n";
						st = __SET->getShipTemplate(
								Menu::print(__SET->getShipTemplateNames(), true, "select")-1);
					}
					catch ( invalid_argument e ) { break;}

					// Enter the Ship's name
					string shipName;
					cout << "Please insert the Ship's name:\n\n";
					getline(cin, shipName); cout << "\n";

					// Select Ship's Thrust
					int shipThrust;
					cout << "Please insert the Ship's thrust:\n\n";
					shipThrust = Menu::getValidInt(MIN_THRUST,MAX_THRUST);
					
					// Select Ship's Fleet
					int fleet;
					cout << "Please select the Ship's fleet:\n\n";
					fleet = Menu::print(__DEP->getFleetNames(),false,"select")-1;

					// Create Ship
					__DEP->addShip(shipName,shipThrust,*st, fleet); 
				}
				break;
		// Copy Ship 
		case 7:
			try {
				// Select Base Ship
				const Ship * copied;
				try {
					cout << "Please select the Ship to copy:\n\n";
					copied = __DEP->getShip(Menu::print(__DEP->getShipNames(),true, "select")-1);
				}
				catch ( out_of_range e ) { break; }

				// Select Ship's Name
				string shipName;
				cout << "Please insert the Ship's name\n\n";
				getline(cin, shipName); cout << "\n";

				// Select Ship's Fleet
				int fleet;
				cout << "Please select the Ship's fleet:\n\n";
				fleet = Menu::print(__DEP->getFleetNames(),false,"select")-1;

				__DEP->addShip(shipName, copied->getThrust(), *(copied->getShipTemplate()), fleet);
			} catch ( exception e ) {}
			break;
		// Remove Ship
		case 8:
			try {
				__DEP->rmShip(Menu::print(__DEP->getShipNames(),true,"remove")-1);	
			} catch ( out_of_range e ) {}

			break;
		// Select Ship
		case 9:
			fsm->transitionTo("InspectShip");
			break;
		// Swap Ship's Fleet
		case 10:
			int temp = Menu::print(__DEP->getShipNames(),true,"move")-1;
			if ( temp < 0 ) break;
			try {
				__DEP->mvShip( temp, Menu::print(__DEP->getFleetNames(),false,"select")-1);
			} catch ( out_of_range e ) {}
			break;
	}
	return choice;
}

//-------------------------------------------
// INSPECT FLEET
//------------------------------------------


InspectFleet::InspectFleet(FSM * _fsm) : FSMState(_fsm) {
	stateName="InspectFleet";
	selectedFleet = -1;	
}

void InspectFleet::doEnter(int param) {
	try {
		selectedFleet = Menu::print(__DEP->getFleetNames(), true, "inspect") -1;
		Table::print(*(__DEP->getFleet(selectedFleet)));
	}
	catch (out_of_range e) {
		selectedFleet = -1;
	}
}

int InspectFleet::update() {
	if ( selectedFleet == -1 ) {
		fsm->transitionBack();
		return 0;
	}

	int renameOpt = 1, tSetOptions = 1;
	vector<string> choices = { "reprint the Fleet" };
	
	if ( selectedFleet != 0 ) {
		renameOpt = 0;
		choices.push_back( "rename the Fleet" ); // Can't rename unlisted
	}
	
	if ( __SET && __SET->getShipTemplatesNumber() > 0 ) {
		choices.push_back("add a new Ship");
		tSetOptions = 0;
	}
	else
		cout << "Note that you can't add any Ships to this Fleet because you do not have any available Ship Templates to build them from!\n\n";

	if ( __DEP->getFleet(selectedFleet)->getShipsNumber() > 0 ) {
		choices.push_back( "copy a Ship" );
		choices.push_back("select a Ship");
		choices.push_back("move a Ship to another Fleet");
		choices.push_back("delete a Ship");
	}

	int choice = Menu::print(choices, true);
	
	choice = choice > 1 ? choice + renameOpt : choice;
	choice = choice > 2 ? choice + tSetOptions : choice;

	switch ( choice ) {
		// Return
		case 0:
			fsm->transitionBack();
			break;
		// Reprint
		case 1:
			Table::print(*(__DEP->getFleet(selectedFleet)));
			break;
		// Rename
		case 2:
			{
			string newName;
			cout << "Please insert the new Fleet's name\n\n";
			getline(cin, newName); cout << "\n";
			__DEP->renameFleet(selectedFleet, newName);
			}
			break;
		// Add new Ship
		case 3: {
					const ShipTemplate * st;
					// Select Ship's Template
					try {
						cout << "Please select the Ship's Template:\n\n";
						st = __SET->getShipTemplate(
								Menu::print(__SET->getShipTemplateNames(), true, "select")-1);
					}
					catch ( invalid_argument e ) { break;}

					// Enter the Ship's name
					string shipName;
					cout << "Please insert the Ship's name:\n\n";
					getline(cin, shipName); cout << "\n";

					// Select Ship's Thrust
					int shipThrust;
					cout << "Please insert the Ship's thrust:\n\n";
					shipThrust = Menu::getValidInt(MIN_THRUST,MAX_THRUST);
					
					// Create Ship
					__DEP->addShip(shipName,shipThrust,*st, selectedFleet); 
				}
			break;
		// Copy Ship
		case 4: {
			try {
				// Select Base Ship
				const Ship * copied;
				try {
					cout << "Please select the Ship to copy:\n\n";
					copied = __DEP->getShip(Menu::print(__DEP->getFleet(selectedFleet)->getShipNames(), true,"select")-1, selectedFleet);
				}
				catch ( out_of_range e ) { break; }

				// Select Ship's Name
				string shipName;
				cout << "Please insert the Ship's name\n\n";
				getline(cin, shipName); cout << "\n";

				// Select Ship's Fleet
				cout << "Please select the Ship's fleet:\n\n";
				int fleet = Menu::print(__DEP->getFleetNames(),false,"select")-1;

				__DEP->addShip(shipName, copied->getThrust(), *(copied->getShipTemplate()), fleet);
			} catch ( exception e ) {}
		}
			break;
		// Select Ship
		case 5:
			fsm->transitionTo("InspectShip", selectedFleet);
			break;
		// Move ship
		case 6:
			{
				cout << "Please select the Ship to move:\n\n";
				int toMove = Menu::print(__DEP->getFleet(selectedFleet)->getShipNames(), true,"select")-1;
				if (toMove < 0 ) break;

				cout << "Please select the target Fleet:\n\n";
				int target = Menu::print(__DEP->getFleetNames(),false,"select")-1;

				__DEP->mvShip(toMove, selectedFleet, target);
			}
			break;
		// Delete Ship
		case 7:
			{
				cout << "Please select the Ship to move:\n\n";
				int toDel = Menu::print(__DEP->getFleet(selectedFleet)->getShipNames(), true,"select")-1;
				if (toDel < 0 ) break;

				__DEP->rmShip(toDel, selectedFleet);
			}
			break;
	}
	return choice;
}

//-------------------------------------------
// INSPECT SHIP
//------------------------------------------

#define __SHIP (selFleet == -1 ? __DEP->getShip(selShip) : __DEP->getShip(selShip,selFleet))

InspectShip::InspectShip(FSM * _fsm) : FSMState(_fsm) {
	stateName="InspectShip";
	selShip = -1;	
	selFleet = -1;
}

void InspectShip::doEnter(int param) {
	selShip = -1;
	selFleet = param;

	if ( selFleet != -1 ) {
		try {
			selShip = Menu::print(__DEP->getFleet(selFleet)->getShipNames(), true, "inspect")-1;
			Table::print(*(__DEP->getShip(selShip,selFleet)));
		}
		catch ( out_of_range e ) {}
	}
	else {
		try {
			selShip = Menu::print(__DEP->getShipNames(), true, "inspect") -1;
			Table::print(*(__DEP->getShip(selShip)));
		}
		catch (out_of_range e) {}
	}
}

int InspectShip::update() {
	if ( selShip == -1 ) { fsm->transitionBack(); return 0; }

	int slotChoice = 1;

	vector<string> choices = { "reprint the Ship", "rename the Ship", "print the Ship's status"};
	
	if ( __SHIP->getShipTemplate()->getSlotTemplatesNumber() ) {
		choices.push_back("edit some Slot's loadout");
		slotChoice = 0;
	}

	if ( __SHIP->getShipTemplate()->getTowers() )
		choices.push_back("edit some Tower's loadout");
	
	int choice = Menu::print(choices, true);

	choice = choice > 3 ? choice + slotChoice : choice;
	
	switch ( choice ) {
		// Return
		case 0:
			fsm->transitionBack();
			break;
		// Reprint
		case 1:
			Table::print(*__SHIP);
			break;
		// Rename
		case 2:
			{
			string newName;
			cout << "Please insert the new Ship's name\n\n";
			getline(cin, newName); cout << "\n";
			__DEP->renameShip(selShip, newName);
			}
			break;
		// Print Status
		case 3:
			Table::printStat(*__SHIP);
			break;
		// Edit Slot Loadout
		case 4: {
			cout << "Edit Slot Loadout\n\n";
			int side;
			{
				vector<string> choices = {"FRONT", "FRONT RIGHT", "BACK RIGHT", 
										  "BACK", "BACK LEFT", "FRONT LEFT" };
				side = Menu::print(choices, true, "edit the");
			}

			if ( side == 0 ) break;
			side--;

			int slot;
			{
				// Build choice list
				vector<string> slots;
				int nSlots = __SHIP->getShipTemplate()->getSlotTemplatesNumber(side);

				int baseInt = 1;
				for (int i=0; i < side; i++ )
					baseInt += __SHIP->getShipTemplate()->getSlotTemplatesNumber(i);

				for (int i=0; i < nSlots;i++)
					slots.push_back("Slot [" + std::to_string(baseInt+i) + "]" );
				// Print it	
				slot = Menu::print(slots, true, "edit") - 1;
			}
			if ( slot == -1 ) break;

			int loadout;
			{
				// Build choice list
				vector<string> choices;
				for ( int i = MIN_DEFINED_FILL_TYPE; i < MAX_DEFINED_FILL_TYPE+1; i++ )
					choices.push_back(STRING_FILL(i));

				loadout = Menu::print(choices, true, "select") - 1;
			}
			if ( loadout == -1 ) break;

			try {
				if ( selFleet == -1 )
					__DEP->editShipLoadout(selShip,side,slot,loadout);
				else
					__DEP->editShipLoadout(selShip,selFleet,side,slot,loadout);
			}
			catch ( runtime_error e ) {
				cout << "The selected Slot doesn't support the loadout type chosen!\n\n";
			}

			break;
		}
		// Edit Tower Loadout
		case 5: {
			int tower;
			{
				// Build choice list
				vector<string> towers;
				int nTowers = __SHIP->getShipTemplate()->getTowers();

				for (int i=0; i < nTowers;i++)
					towers.push_back("Tower [" + std::to_string(i+1) + "]" );
				// Print it	
				tower = Menu::print(towers, true, "edit") - 1;
			}
			if ( tower == -1 ) break;

			int loadout;
			{
				// Build choice list
				vector<string> choices;
				for ( int i = MIN_TOWER; i < MAX_TOWER+1; i++ )
					choices.push_back(STRING_TOWER(i));

				loadout = Menu::print(choices, true, "select") - 1;
			}
			if ( loadout == -1 ) break;

			if ( selFleet == -1 )
				__DEP->editShipTower(selShip,tower,loadout);
			else
				__DEP->editShipTower(selShip,selFleet,tower,loadout);

			break;
		}
	}
	return 0;
}
#undef __SHIP




#undef __DEP
#undef __SET

